--[[ 
A container for Item objects. Container classes should extend this class and redefine the 
'can_hold' and 'capacity' attributes.
--]]
Container = class("Container")
Container.can_hold = set(Item)  -- set of item types that can be placed inside container
Container.capacity = math.huge  -- maximum number of objects that container may hold

function Container.__init(self)
    self.contents = list()
    self.occupied = 0
end

function Container.__tostring(self)
    return ("<%s (%s / %s) %s>"):format(self.__name, self.occupied, self.capacity, self.contents)
end

function Container.is_full(self)
    return self.occupied >= self.capacity
end

function Container.can_receive(self, item)
    --[[ Returns True if the item can be placed inside the container. ]]
    return self:_can_receive(item) == nil
end

function Container._can_receive(self, item)
    --[[ Returns None if the given item can be put inside the container, otherwise returns an 
    error message indicating the reason why the item can't be placed in the container. ]]
    if item is self:
        return "container cannot hold itself"
    if item.location is self:
        return "item is already inside the container"
    if not any(klass in self.can_hold for klass in type(item).mro()):
        return "container is unable to hold objects of the given type"
    if self.occupied + item.occupies > self.capacity:
        return "insufficient capacity in container"
    return None
    
def add(self, item):
    """Add an item to the container."""
    self._add(item)
    item._set_location(self)
    
def _add(self, item):
    message = self._can_receive(item)
    if message is not None:
        raise ValueError(message)
    self.contents.append(item)
    self.occupied += item.occupies
    
def remove(self, item):
    """Remove an item from the container."""
    self._remove(item)
    item._clear_location()
    
def _remove(self, item):
    self.contents.remove(item)
    self.occupied -= item.occupies
    
def init(self, game):
    """Called at game start. This is when objects register themselves with the game object 
    registry."""
    for item in self.contents:
        item.init(game)
            
          
--[[          
class Item(object):
    """Base class for most game objects that represent physical pieces, e.g. units, facilities, 
    or other tokens that are used during game play."""
    occupies = 1.0  # represents how much space the item takes inside a container
    
    def __init__(self, owner=None, location=None):
        self.owner    = None  # a reference to the Player that owns this object
        self.location = None  # Items are located inside Containers
        if owner is not None:
            self.set_owner(owner)        # set owner using the item/player interface
        if location is not None:
            self.set_location(location)  # set location using the item/container interface
            
    def __repr__(self):
        owner = "Neutral" if self.owner is None else "%s's" % self.owner.name
        return "<%s %s @ %s-%s (%08x)>" % (owner,
                                           self.__class__.__name__, 
                                           self.coords, 
                                           self.side.name, 
                                           id(self))
        
    def set_owner(self, owner):
        """Set an item's owner (a Player object or None)."""
        if owner is None:
            if self.owner is not None:
                self.owner._remove(self)
            self._clear_owner()
        else:
            owner._add(self)
            self._set_owner(owner)
            
    def _set_owner(self, owner):
        if owner is None:
            raise Exception("non-null owner required")
        if self.owner is not None:
            self.owner._remove(self)
        self.owner = owner
        
    def _clear_owner(self):
        self.owner = None
        
    def set_location(self, location):
        """Set an item's location to a Container object or None."""
        if location is None:
            if self.location is not None:
                self.location._remove(self)
            self._clear_location()
        else:
            location._add(self)
            self._set_location(location)
            
    def _set_location(self, location):
        if location is None:
            raise Exception("non-null location required")
        if self.location is not None:
            self.location._remove(self)
        self.location = location
        
    def _clear_location(self):
        self.location = None
        
    def init(self, game):
        """Called at game start. Items should register themselves in the game object registry."""
        assert game is self.game
        self.register()
        
    def destroy(self):
        """Remove an item from the game."""
        if self.game is not None:
            self.unregister()
        self.set_location(None)
        self.set_owner(None)
        
    def register(self):
        """This method should be called at game start for all items in the initial board, or 
        whenever new items are created during the game, to register objects in the game object 
        database."""
        self.game.objects.add(self)
        
    def unregister(self):
        """This method should only be called when an object is removed from the game, to 
        unregister it from the game's database."""
        self.game.objects.remove(self)
        
    # -------------------------------------------
    # PROPERTIES
    @property
    def x(self):
        """Returns the x coordinate of the tile where the item is located."""
        return None if self.location is None else self.location.x
        
    @property
    def y(self):
        """Returns the y coordinate of the tile where the item is located."""
        return None if self.location is None else self.location.y
        
    @property
    def coords(self):
        """Goes up the object tree until a tile is found, and returns the tile's coordinates."""
        return None if self.location is None else self.location.coords
        
    @property
    def direction(self):
        """Goes up the object tree until a Side object is found, and returns its direction."""
        return None if self.location is None else self.location.direction
        
    @property
    def side(self):
        """Returns the tile side where the item is located."""
        return None if self.location is None else self.location.side
        
    @property
    def tile(self):
        """Returns the Tile where the item is located."""
        return None if self.location is None else self.location.tile 
        
    @property
    def map(self):
        """Returns the Map object (indirectly) containing the item."""
        return None if self.location is None else self.location.map
        
    @property
    def game(self):
        """Returns the Game object to which the item is associated."""
        return None if self.location is None else self.location.game
        ]]